﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Reflection;
using System.IO;
using System.Collections;
using System.Diagnostics;
using AmiEmuLib.Configuration.AmigaEventsConfig;
using AmiEmuLib.Configuration.EventBindingConfig;
using log4net;
using AmiEmuLib.Host.HostInput;
using TomanuExtensions;

namespace AmiEmuLib.Configuration.EventBindingConfig
{
    /// <summary>
    /// Link events from host to amiga for single devices.
    /// </summary>
    [DebuggerDisplay("Name = {Name}")]
    public class EventsBinding
    {
        /// <summary>
        /// Logger.
        /// </summary>
        private static readonly ILog Logger = LogManager.GetLogger(typeof(EventsBinding));

        /// <summary>
        /// List of event bindings.
        /// </summary>
        private List<EventBinding> m_bindings = new List<EventBinding>();

        /// <summary>
        /// True for default bindings.
        /// </summary>
        public readonly bool ReadOnly;

        private string m_name;

        /// <summary>
        /// To identify bindings.
        /// </summary>
        public Guid Guid { get; internal set; }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_reader"></param>
        internal EventsBinding(XmlReader a_reader, bool a_read_only) 
        {
            ReadOnly = a_read_only;

            m_name = a_reader.GetAttribute("Name");
            Guid = a_reader.GetAttributeGuid("Guid");

            if (!Default)
                a_reader.GetAttribute("DisplayName");
            
            if (a_reader.IsEmptyElement)
                a_reader.MoveToNextElement(GetType().Name);
            else
            {
                a_reader.ReadStartElement(GetType().Name);

                while (a_reader.IsStartElement())
                {
                    EventBinding event_binding = new EventBinding(a_reader, a_read_only);
                    if (event_binding.AmigaEvent != null)
                        m_bindings.Add(event_binding);
                }

                a_reader.ReadEndElement();
            }

            if (!a_read_only)
            {
                Check();
                Sort();
            }
        }

        /// <summary>
        /// Try to remove all possible errors in xml.
        /// </summary>
        private void Check()
        {
            AmigaEvents aevs = FindAmigaEvents();

            var dups = (from b in m_bindings
                        group b by b.AmigaEvent into gr
                        where gr.Count() > 1
                        from b in gr.Skip(1)
                        select b).ToList();

            if (dups.Count != 0)
            {
                Logger.ErrorFormat("Duplicated AmigaEvents finded in {0}", Name);
                m_bindings.RemoveRange(dups);
            }

            foreach (var b in m_bindings.Where(b => !aevs.Events.Contains(b.AmigaEvent)).ToList())
            {
                Logger.ErrorFormat("Removing EventBinding '{0}' from {1} ", b.AmigaEvent.Name, Name);
                m_bindings.Remove(b);
            }

            foreach (var aev in aevs.Events.Where(ev => !m_bindings.Select(b => b.AmigaEvent).Contains(ev)))
            {
                Logger.ErrorFormat("Adding EventBinding '{0}' to {1} ", aev.Name, Name);
                m_bindings.Add(new EventBinding(aev));
            }
        }

        /// <summary>
        /// Constuctor.
        /// </summary>
        /// <param name="a_name"></param>
        /// <param name="a_source"></param>
        internal EventsBinding(string a_name, EventsBinding a_source)
        {
            m_name = a_name;
            Guid = Guid.NewGuid();
            ReadOnly = false;

            foreach (var b in a_source.Bindings)
                m_bindings.Add(new EventBinding(b));
        }

        /// <summary>
        /// Sort bindings according to amiga events xml.
        /// </summary>
        private void Sort()
        {
            List<EventBinding> bindings = new List<EventBinding>(m_bindings);
            var aevs = FindAmigaEvents().Events;
            m_bindings.Clear();
            foreach (var aev in aevs)
                m_bindings.Add(bindings.FirstOrDefault(b => b.AmigaEvent == aev));
            m_bindings.AddRangeUnique(bindings);
        }

        /// <summary>
        /// List of event bindings.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<EventBinding> Bindings
        {
            get
            {
                return m_bindings;
            }
        }

        /// <summary>
        /// Write to xml.
        /// </summary>
        /// <param name="a_writer"></param>
        internal void WriteXml(XmlWriter a_writer)
        {
            a_writer.WriteStartElement(GetType().Name);

            a_writer.WriteAttribute("Name", Name);

            a_writer.WriteAttribute("Guid", Guid);

            if (!Default)
            {
                foreach (var eb in m_bindings)
                    eb.WriteXml(a_writer);
            }

            a_writer.WriteEndElement();
        }

        /// <summary>
        /// Get amiga events for this events bindings. 
        /// </summary>
        /// <returns></returns>
        public AmigaEvents GetAmigaEvents()
        {
            var events = m_bindings.Select(b => b.AmigaEvent).ToList();

            foreach (AmigaEvents evs in AmigaEventsList.Events)
            {
                if (evs.Events.Exact(events))
                    return evs;
            }

            return null;
        }

        /// <summary>
        /// Find amiga events with best match.
        /// </summary>
        /// <returns></returns>
        internal AmigaEvents FindAmigaEvents()
        {
            List<AmigaEvent> avs;

            var d = EventsBindingList.List.FirstOrDefault(b => b.Guid == Guid);
            if (d != null)
                avs = d.Bindings.Select(b => b.AmigaEvent).ToList();
            else
                avs = m_bindings.Select(b => b.AmigaEvent).ToList();

            int match = -1;
            AmigaEvents result = null;

            foreach (var events in AmigaEventsList.Events)
            {
                int m = events.Events.Intersect(avs).Count();

                if (m > match)
                {
                    match = m;
                    result = events;
                }
            }

            return result;
        }

        /// <summary>
        /// Display name. Should be unique.
        /// </summary>
        public string Name 
        {
            get
            {
                return m_name;
            }
            set
            {
                if (ReadOnly)
                {
                    Logger.Error("EventsBindings is read only");
                    return;
                }

                if (EventsBindingList.List.Except(this).Any(b => b.m_name == value))
                {
                    Logger.Error("Name is not unique");
                    return;
                }

                m_name = value;
            }
        }

        /// <summary>
        /// Build in bindings.
        /// </summary>
        private bool Default
        {
            get
            {
                return DefaultEventsBindings.List.Any(b => b.Guid == Guid);
            }
        }
    }
}
